#include "kvm.h"
#include "kconfig.h"
#include "kopcodes.h"
#include "kerror.h"

#include <iostream>
#include <cstring>
#include <cstdio>   // printf
#include <cmath>
#include <cstdlib>  // abs

// KVM class's members initialization

bool KVM::IsRunning = false;
k_int KVM::Line = 0;
k_uint KVM::IP = 0;

const unsigned char *KVM::Code = NULL;
const char *KVM::Pool = NULL;
k_uint KVM::CodeSize = 0;

std::map<k_int, KObject> *KVM::Variables = NULL;
std::stack<KObject> *KVM::evalStack = NULL;
std::stack<KObject> *KVM::kStack = NULL;
KObject *KVM::kTape = NULL;
k_int KVM::kTapePtr = 0;

std::stack<k_uint> *KVM::CallStack = NULL;

KObject *KVM::EmptyString = NULL;

OP_EXECUTOR KVM::Executors[256];

OP_EXECUTOR KVM::DefaultExecutors[] =
{
	KVM::do_nop,    // no operation
	KVM::do_ldi,    // load int
	KVM::do_ldr,    // load real
	KVM::do_lds,    // load string
	KVM::do_ldes,   // load empty string
	KVM::do_ldvar,  // load variable
	KVM::do_stvar,  // store variable
	KVM::do_new,    // new real variable
	KVM::do_free,   // delete object variable
	KVM::do_cvi,    // convert to int
	KVM::do_cvr,    // convert to real
	KVM::do_cvs,    // convert to string
	KVM::do_cvin,   // convert to int, Nothrow
	KVM::do_cvrn,   // convert to real, Nothrow
	KVM::do_in,     // standard input
	KVM::do_out,    // standard output
	KVM::do_push,   // push to k-stack
	KVM::do_pop,    // pop from k-stack
	KVM::do_stf,    // stack transfer (k-stack to eval-stack)
	KVM::do_smv,    // stack move (k-stack to eval-stack)
	KVM::do_swap,   // stack swap (k-stack)
	KVM::do_sclr,   // stack clear (k-stack)
	KVM::do_sclrc,  // stack clear, counted (k-stack)
	KVM::do_trd,    // tape read
	KVM::do_trdx,   // tape read at indeX
	KVM::do_twr,    // tape write
	KVM::do_twrx,   // tape write at indeX
	KVM::do_tnxt,   // tape next
	KVM::do_tprv,   // tape previous
	KVM::do_tmov,   // tape move
	KVM::do_jmp,    // jump
	KVM::do_cjmp,   // conditional jump
	KVM::do_call,   // call
	KVM::do_ccall,  // conditional call
	KVM::do_ret,    // return
	KVM::do_hlt,    // halt (terminate the program)

	KVM::do_add,    // addition
	KVM::do_sub,    // subtraction
	KVM::do_mul,    // multiplication
	KVM::do_div,    // division
	KVM::do_idiv,   // integral division
	KVM::do_mod,    // modulus
	KVM::do_pow,    // exponentiation
	KVM::do_equ,    // equal
	KVM::do_neq,    // not equal
	KVM::do_lt,     // less than
	KVM::do_lte,    // less than or equal
	KVM::do_gt,     // greater than
	KVM::do_gte,    // greater than or equal
	KVM::do_cat,    // concatenate (string)
	KVM::do_and,    // logical and
	KVM::do_or,     // logical or
	KVM::do_xor,    // logical exclusive or
	KVM::do_not,    // logical not
	KVM::do_neg,    // negation
	KVM::do_func,   // built-in function

	KVM::do_trace   // for debug mode
};

FUNC_EXECUTOR KVM::BuiltinFunctions[] =
{
	KVM::f_abs,
	KVM::f_sqrt,
	KVM::f_sin,
	KVM::f_cos,
	KVM::f_tan,
	KVM::f_asin,
	KVM::f_acos,
	KVM::f_atan,
	KVM::f_chr,
	KVM::f_asc,
	KVM::f_len,
	KVM::f_left,
	KVM::f_mid,
	KVM::f_right,
	KVM::f_iif,
	KVM::f_tape,
	KVM::f_stack,
	KVM::f_pop,
	KVM::f_peek
};

void KVM::do_nop()
{
	++IP;
}

void KVM::do_ldi()
{
	k_int i = 0;
	
	// skips op_ldi
	++IP;

	// takes the k_int
	i = *(k_int *)(Code + IP);

	// increments IP
	IP += sizeof(k_int);

	evalStack->push(KObject(i));
}

void KVM::do_ldr()
{
	k_real r = (k_real)0;

	++IP;
	r = *(k_real *)(Code + IP);
	IP += sizeof(k_real);

	evalStack->push(KObject(r));
}

void KVM::do_lds()
{
	k_int offset = 0;
	
	++IP;
	offset = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	evalStack->push(KObject(k_string(Pool+offset)));
}

void KVM::do_ldes()
{
	++IP;

	evalStack->push(*KVM::EmptyString);
}

void KVM::do_ldvar()
{
	k_int h = 0;

	++IP;
	h = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	evalStack->push((*Variables)[h]);
}

void KVM::do_stvar()
{
	k_int h = 0;

	++IP;
	h = *(k_int *)(Code + IP);
	IP += sizeof(k_int);
	
	(*Variables)[h] = evalStack->top();
	evalStack->pop();
}

void KVM::do_new()
{
	k_int h = 0;

	++IP;
	h = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	(*Variables)[h] = evalStack->top();
	evalStack->pop();
}

void KVM::do_free()
{
	k_int h = 0;

	++IP;
	h = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	Variables->erase(h);
}

void KVM::do_cvi()
{
	++IP;

	KObject obj(evalStack->top());
	evalStack->pop();

	if (obj.Type == K_STRING)
		throw KRuntimeError(KRE_CANT_CVI, Line);

	evalStack->push(KObject(obj.ToInt()));
}

void KVM::do_cvr()
{
	++IP;

	KObject obj(evalStack->top());
	evalStack->pop();

	if (obj.Type == K_STRING)
		throw KRuntimeError(KRE_CANT_CVR, Line);

	evalStack->push(KObject(obj.ToReal()));
}

void KVM::do_cvs()
{
	++IP;

	KObject obj(evalStack->top().ToString());
	evalStack->pop();

	evalStack->push(obj);
}

void KVM::do_cvin()
{
	++IP;

	KObject obj(evalStack->top().ToInt());
	evalStack->pop();

	evalStack->push(obj);
}

void KVM::do_cvrn()
{
	++IP;

	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(obj);
}

void KVM::do_in()
{
	++IP;

	k_string s;
	std::getline(std::cin, s);

	evalStack->push(KObject(s));
}

void KVM::do_out()
{
	++IP;

	printf("%s", evalStack->top().ToString().c_str());
	evalStack->pop();
}

void KVM::do_push()
{
	++IP;

	kStack->push(evalStack->top());
	evalStack->pop();
}

void KVM::do_pop()
{
	++IP;

	if (kStack->empty())
		throw KRuntimeError(KRE_STACK_EMPTY, Line);

	kStack->pop();
}

void KVM::do_stf()
{
	++IP;

	if (kStack->empty())
		throw KRuntimeError(KRE_STACK_EMPTY, Line);

	evalStack->push(kStack->top());
}

void KVM::do_smv()
{
	++IP;

	if (kStack->empty())
		throw KRuntimeError(KRE_STACK_EMPTY, Line);

	evalStack->push(kStack->top());
	kStack->pop();
}

void KVM::do_swap()
{
	++IP;

	KObject a, b;

	if (kStack->size() < 2)
		throw KRuntimeError(KRE_STACK_EMPTY, Line);

	a = kStack->top(); kStack->pop();
	b = kStack->top(); kStack->pop();

	kStack->push(a);
	kStack->push(b);
}

void KVM::do_sclr()
{
	++IP;

	while (!kStack->empty())
		kStack->pop();
}

void KVM::do_sclrc()
{
	k_int c = 0;

	++IP;
	c = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	int i;
	for (i = 0; i < c && !kStack->empty(); ++i)
		kStack->pop();

	if (i < c)
		throw KRuntimeError(KRE_STACK_EMPTY, Line);
}

void KVM::do_trd()
{
	++IP;

	evalStack->push(kTape[kTapePtr]);
}

void KVM::do_trdx()
{
	k_int idx = 0;

	++IP;
	idx = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	if (idx < 0 || idx >= MAX_TAPE)
		throw KRuntimeError(KRE_TAPE_INDEX_OOR, Line);

	evalStack->push(kTape[idx]);
}

void KVM::do_twr()
{
	++IP;

	kTape[kTapePtr] = evalStack->top();
	evalStack->pop();
}

void KVM::do_twrx()
{
	k_int idx = 0;

	++IP;
	idx = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	if (idx < 0 || idx >= MAX_TAPE)
		throw KRuntimeError(KRE_TAPE_INDEX_OOR, Line);

	kTape[idx] = evalStack->top();
	evalStack->pop();
}

void KVM::do_tnxt()
{
	++IP;

	if (kTapePtr < MAX_TAPE - 1)
		++kTapePtr;
	else
		kTapePtr = 0;
}

void KVM::do_tprv()
{
	++IP;

	if (kTapePtr > 0)
		--kTapePtr;
	else
		kTapePtr = MAX_TAPE - 1;
}

void KVM::do_tmov()
{
	++IP;

	KObject obj(evalStack->top());
	evalStack->pop();

	k_int idx = obj.v_int;
	if (idx < 0 || idx >= MAX_TAPE)
		throw KRuntimeError(KRE_TAPE_INDEX_OOR, Line);
	
	kTapePtr = idx;
}

void KVM::do_jmp()
{
	k_uint offset = 0;

	++IP;
	offset = *(k_int *)(Code + IP);

	IP = offset;
}

void KVM::do_cjmp()
{
	k_uint offset = 0;

	++IP;
	offset = *(k_int *)(Code + IP);
	IP += sizeof(k_uint);

	KObject obj(evalStack->top());
	evalStack->pop();

	if (obj)
		IP = offset;
}

void KVM::do_call()
{
	k_uint offset = 0;

	++IP;
	offset = *(k_int *)(Code + IP);

	CallStack->push(IP + sizeof(k_int));
	IP = offset;
}

void KVM::do_ccall()
{
	k_uint offset = 0;

	++IP;
	offset = *(k_int *)(Code + IP);
	IP += sizeof(k_uint);

	KObject obj(evalStack->top());
	evalStack->pop();

	if (obj)
	{
		CallStack->push(IP);
		IP = offset;
	}
}

void KVM::do_ret()
{
	if (CallStack->empty())
	{
		IP = 0;
	}
	else
	{
		IP = CallStack->top();
		CallStack->pop();
	}
}

void KVM::do_hlt()
{
	++IP;

	IsRunning = false;
}

void KVM::do_add()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a + b);
}

void KVM::do_sub()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a - b);
}

void KVM::do_mul()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a * b);
}

void KVM::do_div()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a / b);
}

void KVM::do_idiv()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(KObject::IntDiv(a, b));
}

void KVM::do_mod()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a % b);
}

void KVM::do_pow()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(KObject::Pow(a, b));
}

void KVM::do_equ()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	evalStack->push(a == b);
}

void KVM::do_neq()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	evalStack->push(a != b);
}

void KVM::do_lt()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a < b);
}

void KVM::do_lte()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a <= b);
}

void KVM::do_gt()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a > b);
}

void KVM::do_gte()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	if (KObject::HigherType(a.Type, b.Type) == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(a >= b);
}

void KVM::do_cat()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	evalStack->push(KObject::Concat(a, b));
}

void KVM::do_and()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	evalStack->push(a && b);
}

void KVM::do_or()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	evalStack->push(a || b);
}

void KVM::do_xor()
{
	++IP;

	KObject a, b;

	b = evalStack->top(); evalStack->pop();
	a = evalStack->top(); evalStack->pop();

	evalStack->push(a ^ b);
}

void KVM::do_not()
{
	++IP;

	KObject a(evalStack->top());
	evalStack->pop();

	evalStack->push(!a);
}

void KVM::do_neg()
{
	++IP;

	KObject a(evalStack->top());
	evalStack->pop();

	if (a.Type == K_STRING)
		throw KRuntimeError(KRE_TYPE_MISMATCHED, Line);

	evalStack->push(-a);
}

void KVM::do_func()
{
	k_int fidx = 0;

	++IP;
	fidx = *(k_int *)(Code + IP);
	IP += sizeof(k_int);

	BuiltinFunctions[fidx]();
}

void KVM::do_trace()
{
	++IP;
	Line = *(k_int *)(Code + IP);
	IP += sizeof(k_int);
}

void KVM::do_illegal()
{
	throw KRuntimeError(KRE_ILLEGAL_OPCODE);
}

void KVM::f_abs()
{
	KObject obj(evalStack->top());
	evalStack->pop();

	if (obj.Type == K_INT)
		evalStack->push(KObject(abs(obj.v_int)));
	else
		evalStack->push(KObject(fabs(obj.v_real)));
}

void KVM::f_sqrt()
{
	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(KObject(sqrt(obj.v_real)));
}

void KVM::f_sin()
{
	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(KObject(sin(obj.v_real)));
}

void KVM::f_cos()
{
	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(KObject(cos(obj.v_real)));
}

void KVM::f_tan()
{
	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(KObject(tan(obj.v_real)));
}

void KVM::f_asin()
{
	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(KObject(asin(obj.v_real)));
}

void KVM::f_acos()
{
	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(KObject(acos(obj.v_real)));
}

void KVM::f_atan()
{
	KObject obj(evalStack->top().ToReal());
	evalStack->pop();

	evalStack->push(KObject(atan(obj.v_real)));
}

void KVM::f_chr()
{
	k_int c = evalStack->top().ToInt();
	evalStack->pop();

	char ac[1];
	ac[0] = (char)(c % 256);

	evalStack->push(KObject(ac));
}

void KVM::f_asc()
{
	k_string s(evalStack->top().ToString());
	evalStack->pop();

	if (s.empty())
		evalStack->push(KObject((k_int)0));
	else
		evalStack->push(KObject((k_int)s[0]));
}

void KVM::f_len()
{
	k_string s(evalStack->top().ToString());
	evalStack->pop();

	evalStack->push(KObject((k_int)s.length()));
}

void KVM::f_left()
{
	k_string s;
	k_int length;

	length = evalStack->top().ToInt();
	evalStack->pop();

	s = evalStack->top().ToString();
	evalStack->pop();

	if (length >= (k_int)s.length())
		evalStack->push(KObject(s));
	else
		evalStack->push(KObject(s.substr(0, length)));
}

void KVM::f_mid()
{
	k_string s;
	k_int startIndex, length;

	length = evalStack->top().ToInt();
	evalStack->pop();

	startIndex = evalStack->top().ToInt();
	evalStack->pop();

	s = evalStack->top().ToString();
	evalStack->pop();

	k_int slen = s.length();
	if (startIndex >= slen || length >= slen)
		evalStack->push(KObject(s));
	else if (startIndex + length >= slen)
		evalStack->push(KObject(s));
	else
		evalStack->push(KObject(s.substr(startIndex, length)));
}

void KVM::f_right()
{
	k_string s;
	k_int length;

	length = evalStack->top().ToInt();
	evalStack->pop();

	s = evalStack->top().ToString();
	evalStack->pop();

	if (length >= (k_int)s.length())
		evalStack->push(KObject(s));
	else
		evalStack->push(KObject(s.substr(s.length() - length)));
}

void KVM::f_iif()
{
	KObject cond, tv, fv;

	fv = evalStack->top();
	evalStack->pop();

	tv = evalStack->top();
	evalStack->pop();

	cond = evalStack->top();
	evalStack->pop();

	if (cond)
		evalStack->push(tv);
	else
		evalStack->push(fv);
}

void KVM::f_tape()
{
	k_int idx = evalStack->top().ToInt();
	evalStack->pop();

	if (idx < 0 || idx >= MAX_TAPE)
		throw KRuntimeError(KRE_TAPE_INDEX_OOR, Line);

	evalStack->push(kTape[idx]);
}

void KVM::f_stack()
{
	k_int diff = evalStack->top().ToInt();

	if (diff <= 0 || diff > (k_int)kStack->size())
		throw KRuntimeError(KRE_STACK_PTR_OOR, Line);

#ifdef _MSC_VER
	evalStack->push(*(kStack->_Get_container().end() - diff));
#else
	evalStack->push(*( &kStack->top() - (diff - 1) ));
#endif
}

void KVM::f_pop()
{
	if (kStack->empty())
		throw KRuntimeError(KRE_STACK_EMPTY, Line);

	evalStack->push(kStack->top());
	kStack->pop();
}

void KVM::f_peek()
{
	if (kStack->empty())
		throw KRuntimeError(KRE_STACK_EMPTY, Line);

	evalStack->push(kStack->top());
}

void KVM::PrepareExecutors()
{
	static bool prepared = false;

	if (prepared)
		return;

	int i = 0;

	// Each legal opcode has a distinct executor:
	for (; i <= op_trace; ++i)
		KVM::Executors[i] = KVM::DefaultExecutors[i];

	// All illegal opcodes have the same executor:
	for (; i < 256; ++i)
		KVM::Executors[i] = KVM::do_illegal;

	prepared = true;
}

bool KVM::Run(const unsigned char *code, k_uint codeSize, const char *pool)
{
	PrepareExecutors();

	Code = code;
	CodeSize = codeSize;
	Pool = pool;

	IsRunning = true;
	Line = 1;
	IP = 0;

	try
	{
		while (IP < CodeSize && IsRunning)
			Executors [ Code[IP] ] () ;
	}
	catch (KRuntimeError &err)
	{
		if (err.line > 0)
			std::cout << "\tLine " << err.line << ":" << std::endl;

		std::cout << "\tRuntime Error: " << err.errMsg << std::endl;

		return false;
	}
	
	IsRunning = false;

	{	// Clean-up

		k_uint esSize = evalStack->size();
		for (k_uint i = 0; i < esSize; ++i) evalStack->pop();

		k_uint ksSize = kStack->size();
		for (k_uint i = 0; i < ksSize; ++i) kStack->pop();

		Variables->clear();
	}

	return true;
}

void KVM::Init()
{
	KVM::Variables   = new std::map<k_int, KObject>;
	KVM::evalStack   = new std::stack<KObject>;
	KVM::kStack      = new std::stack<KObject>;
	KVM::kTape       = new KObject[MAX_TAPE];
	KVM::CallStack   = new std::stack<k_uint>;
	KVM::EmptyString = new KObject("");
}

void KVM::CleanUp()
{
	delete KVM::Variables;   KVM::Variables = NULL;
	delete KVM::evalStack;   KVM::evalStack = NULL;
	delete KVM::kStack;      KVM::kStack = NULL;
	delete [] KVM::kTape;    KVM::kTape = NULL;
	delete KVM::CallStack;   KVM::CallStack = NULL;
	delete KVM::EmptyString; KVM::EmptyString = NULL;
}
